<?php
class Model_AclAssistant extends RL_Model2 {

	/**
	 * Значения переменной opt в таблице t_acl_list
	 *
	 * @var string
	 **/
	private $act_allowed = 1;
	private $act_disallowed = 2;
	private $act_inherit = 0; // default

	/**
	 * Массив содержащий все данные связанные с доступом в текущей сессии
	 *
	 * @var array
	 **/
	public $acl_data = array();
	private $principal_data;
	protected $available_operations;

	public $template_data = array();
	private $m_tdi;
	protected $acl_instance;

	protected function init() {
		$this->acl_instance = $this->mm->getModel("AclAssistantLowLevel");

		// не забыть где-нибудь указать modelManager'у акл ассистента
	}

	/*
	public function __construct(Acl $acl_instance, RL_SiteRequestHandler $rh, array $params) {
		$this->rh = &$rh;
		$this->debug = $rh->ctx->debug;
		$this->store = &$rh->ctx->store;
		$this->acl_instance = &$acl_instance;
		$this->acl_data = $params;

		// укажем менеджеру моделей на ассистента
		$this->rh->ctx->mm->setAclAssistant( $this );
	}
	*/

	public function setPrincipalData($pd) {
		$this->principal_data = $pd;
	}

	public function setRequestParams(array $params) {
		$this->acl_data = array_merge($this->acl_data, $params);
	}

	/*
	 * Популярная функция. Вызывается в каждом обработчике. Результат работы - заполнение двух полей объекта
	 *  - $acl_data - используется для получения различных данных о праве или статусе
	 *  - $template_data - используется для вывода в шаблоны
	 */
	public function getInfo() {

		/*
			Получается, что в рамках сессии нужно определить:

			1)сообщество, куда обращается юзер
			2)его роль в этом сообществе
			3)разделы определенные для этого сообщества

			Специфичное для запросов:
			1) id итема в хранилище объектов, если запрашивают действия связанные с итемом (приходит, после парсинга url)
			2) id юзера в хранилище объектов (сомнительно)
		*/
		$community_url = isset($this->acl_data['community_url']) ? $this->acl_data['community_url'] : ($this->acl_data['community_url'] = NULL);
		$tdi_url       = isset($this->acl_data['tdi_url']) ? $this->acl_data['tdi_url'] : ($this->acl_data['tdi_url'] = NULL);
		$id_item       = isset($this->acl_data["id_item"]) ? $this->acl_data["id_item"] : ($this->acl_data["id_item"] = NULL);
		if (!isset($this->acl_data["title"])) $this->acl_data["title"] = NULL;
		if (!isset($this->acl_data["mod"])) $this->acl_data["mod"] = NULL;
		if (!isset($this->acl_data["background_image_oid"])) $this->acl_data["background_image_oid"] = NULL;
		if (!isset($this->acl_data["logo_image_oid"])) $this->acl_data["logo_image_oid"] = NULL;

		$this->acl_data['comity_oid'] = $this->getCommunityIdByUrl( $community_url );

		$this->acl_data['user']  = array('user_id' => NULL, 'fullname' => 'anonymous', 'openid_url' => NULL);
		$this->acl_data['user_id']  = NULL;
		$this->acl_data['role']  = array('oid' => NULL, 'name'=>'anonymous');

		$this->acl_data['user'] = $this->principal_data;
		if (isset($this->principal_data['user_id'])) {
			$this->acl_data['user_id'] = $this->principal_data['user_id'];
		}
		if (!isset($this->acl_data['user']['fullname'])) $this->acl_data['user']['fullname'] = 'anonymous';
		if (!isset($this->acl_data['user']['user_id'])) $this->acl_data['user']['user_id'] = NULL;
		if (!isset($this->acl_data['user']['openid_url'])) $this->acl_data['user']['openid_url'] = NULL;

		if ($this->acl_data['comity_oid']) { // если мы в сообществе берем рольв сообществе
			$this->acl_data['role'] = $this->getUserRoleInCommunity( $this->acl_data['user_id'], $this->acl_data['comity_oid'] );
		} else { // если не в сообществе - то на узле
			$this->acl_data['role'] = $this->getUserRole( $this->acl_data['user_id'] );
		}

		// разделы доступные в сообществе, сокращение TDI это TypeDoc Instance
		$this->acl_data['tdi_list'] = $this->getCommunityTdi( $this->acl_data['comity_oid'] );
		$this->acl_data['tdi'] = $this->getCurrentTdi( $tdi_url, $this->acl_data['comity_oid'], $id_item);
		$this->acl_data['tdi_oid'] = ($this->acl_data['tdi']) ? $this->acl_data['tdi']->id : NULL;

		if ($this->acl_data['tdi_oid']) {
			$this->template_data['_tdi'] = array(
				                 'acl' => $this->getAcl($this->acl_data['tdi_oid']),
											 );
		}

		// --- community картинки --- {{{
		//$this->rh->UseClass('stores_ImageStorage');
		$img_obj = new ImageStorage( $this->store );

		// --- community картинки --- }}}

		// для вывода в шаблонные переменные
		if ($this->acl_data['comity_oid']) {
			$this->template_data['community'] = array(
					/*
				                 "url" => $this->acl_data['community_url'],
				                 "mod" => $this->acl_data['mod'],
				               "title" => $this->acl_data['title'],
				          'logo_image' => $img_obj->Get($this->acl_data['logo_image_oid']),
				      'logo_image_oid' => $this->acl_data['logo_image_oid'],
				    'background_image' => $img_obj->Get($this->acl_data['background_image_oid']),
				'background_image_oid' => $this->acl_data['background_image_oid'],
				                 'acl' => $this->getAcl($this->acl_data['comity_oid']),
						   'tdi_count' => count($this->acl_data['tdi_list']),
							 	 'oid' => $this->acl_data['comity_oid'],
					 */
				);
			$this->template_data['community'] = $this->mm->getModel("Page_Community","m");
			$this->template_data['community']->initialize(array('acl_data' => $this->acl_data, 'aa'=>$this));
		}

		// юзерская инфа в шапку
		/*
			if (
					 $this->acl_data['role']['name']=="member"
				|| $this->acl_data['role']['name']=="administrator"
				|| $this->acl_data['role']['name']=="moderator"
			) {
				$membership_status = 1; // член сообщества
			} else {
				$jq = $this->mm->getModel("JoinQueue","m");
				$jq->setCommunity($this->acl_data['comity_oid']);
				if (($this->acl_data['user_id'] >0) && ($jq->inQueue($this->acl_data['user_id'])) ) {
					$membership_status = 2; // заявка в очереди
				} else {
					$membership_status = 0; // не член
				}
			}
		$this->template_data['user'] = array(
			"is_authenticated"=>$this->acl_data['user_id'] ? 1:0,
			"real_name"=>$this->acl_data['user']['fullname'],
			"openid_url"=>$this->acl_data['user']['openid_url'],
			"role_name"=>$this->acl_data['role']['name'],
			"role_title"=>$this->acl_data['role']['title'],
			"membershipstatus"=> $membership_status,
			/ *
			"is_comm_moder"=> (
				   $this->acl_data['role']['name']=="moderator"
				|| $this->acl_data['role']['name']=="administrator"
			)?1:0,
			 * /
			"avatar"=>"images/avatar-100x100.png",
			"oid"=> $this->acl_data['user']['user_id'],
			);
		 */
		$this->template_data['user'] = $this->mm->getModel("Page_User","m");
		$this->template_data['user']->initialize(array('acl_data' => $this->acl_data));
		//$this->template_data['base_url'] = $this->rh->base_url;
		$this->template_data['header'] = array(
			"mod"=>$this->acl_data['mod'],
			"community_name"=>$this->acl_data['title'],
			 );
		$this->mm->rh->UseClass('RL_CyrDate');
		$this->template_data['now'] = RL_CyrDate::newNow();

		$this->template_data['menu'] = $this->getMainMenu();

		return $this->acl_data;
	}

	public function getMainMenu ()
	{
		$menu = array();

		if ($this->acl_data['tdi_list']) { // вывод разделов
			$this->m_tdi = $this->mm->getModel("Tdi");
			foreach ($this->acl_data['tdi_list'] as $tdi) $tdi_oids[] = $tdi['id_object'];
			$menu = $this->m_tdi->getSections( $tdi_oids, false );

			$mcom = $this->mm->getModel("Community");
			$com = $mcom->getCommunity($this->acl_data['comity_oid']);
			if ($com instanceof fobject) {
				// установим массив содержащий порядок разделов
				if (	isset($com->data['sections_order']) &&
					(count($com->data['sections_order']) == count($menu))		) {

						$sects_order = $com->data['sections_order'];
					} else {
						$sects_order = array_keys($menu);
					}
				$new_menu = array();
				foreach ($sects_order as $oid) { $new_menu[] = $menu[$oid]; }
			}

		}
		//В меню выводим все квесты
		if($this->acl_data['comity_oid']) {
			$qmodel = $this->mm->getModel("Quest");
			$menu = $qmodel->getActiveQuests();
			foreach($menu as $m) {
				$color = str_pad((count($new_menu)+1)%7,"2","0", STR_PAD_LEFT);

				$new_menu[] = array(
					'title' => $m['title'],
					'show_in_main_menu' => !$m['hide_from_main_menu'],
					'url'   => 'showquest/'.$m['oid'],
					'view'  => 'showquest',
					'color' => $color,
				);
			}
		}
		return $new_menu;
	} // end of method makeMainMenu

	// функции реестра
	public function regfind( $param ) { // найти параметр
		$ql = "objects where register.param=='".$param."'";
		$rs = $this->store->find($ql);
		if ($rs) {
			while ($obj = $rs->next()) return ($obj->data['val']);
		} else {
			debug($this->store->error);
		}

	}

	public function regstore( $param, $value) { // сохранить параметр
		$rentry = $this->store->create('fregister', array('param' => $param, 'val' => $value));
		$res = $rentry->save();
		$res = $rentry->inSense("register");
	}
	// конец функций реестра

	public function is_granted( $ops=array() ) { // проверит доступна ли хотя бы одна из перечисленных операций
		$access_granted = false;
		if (!is_array($ops)) $ops = array($ops);

		if (is_array($this->available_operations))
			foreach ($ops as $operation) {
				if ((isset($this->available_operations[$operation])) && ($this->available_operations[$operation]['is_allowed']==1)) $access_granted = true;
			} else $access_granted = false;

		return $access_granted;
	}

	//!a manipulator
	public function setAcl ($obj, $actions, $subj=NULL)
	/**
	 * Заменить Acl.
	 *
	 * Меняет все правила. Что не указано -- удалит из БД.
	 *
	 * permit_value:
	 *     0 - запретить
	 *     1 - разрешить
	 *  NULL - не устанавливать
	 *
	 * @param mixed $obj oid объекта
	 * @param array $actions список действий
	 *                       array('action_name' => permit_value, 'action2_name' => permit_value, ...);
	 * @param mixed $subj oid субъекта (если не задан -- принимается текущий юзер)
	 * @access public
	 * @return mixed
	 */
	{
		return $this->_changeAcl($obj, $actions, $subj, false);
	} // end of function setAcl

	//!a manipulator
	public function updateAcl ($obj, $actions, $subj=NULL)
	/**
	 * Обновтиь Acl.
	 *
	 * Меняет лишь указанные правила, остальные не трогает.
	 *
	 * permit_value:
	 *     0 - запретить
	 *     1 - разрешить
	 *  NULL - не устанавливать
	 *
	 * @param mixed $obj oid объекта
	 * @param array $actions array('action_name' => permit_value);
	 * @param mixed $subj oid субъекта (если не задан -- принимается текущий юзер)
	 * @access public
	 * @return mixed
	 */
	{
		return $this->_changeAcl($obj, $actions, $subj, true);
	} // end of function updateAcl

	/**
	 * Получить список действий с описанием над объектом.
	 *
	 * @param mixed $obj - oid объекта про который список действий нужно получить
	 * @param mixed $subj - oid пользователя
	 * @param mixed $comm - oid сообщества
	 * @access public
	 * @return array [acl_name]=>  array(4) {
	 *	["id_subject"]=>string(1) oid субъекта,
	 *  ["action"]=>string(10) acl_name
	 *  ["opt"]=>string(1) "1" - см. свойства этого класса с префиксом act_
	 *  ["is_allowed"]=>int(1) - для удобства проверки в шаблонах
	 *  ['id_object'] - oid объекта
	 *  ['inherited_from_oid'] - если было наследование, то от кого
	 *  }
	 */
	public function getAcl ($obj, $subj=NULL, $comm=NULL)
	{
		if (!isset($obj)) return false;

		if (isset($subj)) {
			if ( isset($comm) ) {
				$role = $this->getUserRoleInCommunity($subj, $comm);
			} else {
				$role = $this->getUserRoleInCommunity($subj, $this->acl_data['comity_oid']);
			}
			$user_oid = $subj;
			$role_oid = $role['oid'];
		} else {
			$user_oid = $this->acl_data['user_id'];
			$role_oid = $this->acl_data['role']['oid'];
		}

		$acl  = $this->acl_instance->getRights($obj, array($user_oid, $role_oid) );

		return $this->getRightsPostprocess($acl);
	} // end of function getAcl

	/**
	 * Получить список действий с описанием над объектом для роли пользователя
	 *
	 * @param mixed $obj_oid - oid объекта про который список действий нужно получить
	 * @param mixed $role_oid - oid роли
	 * @access public
	 * @return array [acl_name]=>  array(4) {
	 *	["id_subject"]=>string(1) oid субъекта,
	 *  ["action"]=>string(10) acl_name
	 *  ["opt"]=>string(1) "1" - см. свойства этого класса с префиксом act_
	 *  ["is_allowed"]=>int(1) - для удобства проверки в шаблонах
	 *  ['id_object'] - oid объекта
	 *  ['inherited_from_oid'] - если было наследование, то от кого
	 *  }
	 */
	public function getAclbyRole ($obj_oid, $role_oid)
	{
		if (!isset($obj_oid)) return false;
		if (!isset($role_oid)) return false;

		$acl = $this->acl_instance->getRights($obj_oid, array($role_oid));

		return $this->getRightsPostprocess($acl);
	} // end of function getAclbyRole

	/**
	 * Здесь происходит вся магия: принимается решение о том наследуется ли действие
	 *
	 * @return void
	 * @author Alexander Sibiryakov
	 **/
	protected function getRightsPostprocess($acl) {
		$result = array();

		if ($acl) foreach($acl as $i=>$val) {
			$name = $val['action'];
			if (isset($result[$name])) continue;

			$result[ $name ] = $val;

			if ($val['opt'] == $this->act_allowed) {
				$result[ $name ]['is_allowed'] = 1;
			} elseif ($val['opt'] == $this->act_disallowed) {
				$result[ $name ]['is_allowed'] = 0;
			} elseif ($val['opt'] == $this->act_inherit) {
				// отправляемся искать - у кого бы унаследовать, нам нужно найти такое же действие где opt != 0
				// нужно понимать что мы тут ждем отсортированный по иерархии наследования список действий,
				// этим сейчас занимается метод Acl::getRights
				foreach ($acl as $k=>$v) {
					if (($v['action']==$name) && ($v['opt']!=$this->act_inherit)) {

						switch ($v['opt']) {
							case ($this->act_allowed):
							$result[ $name ]['is_allowed'] = 1;
							$result[ $name ]['inherited_from_oid'] = $v['id_object'];
							break;
							case ($this->act_disallowed):
							$result[ $name ]['is_allowed'] = 0;
							$result[ $name ]['inherited_from_oid'] = $v['id_object'];
							break;
						}

						break;
					}
				}
			}
		}
		return $result;
	}

	//!an function
	//!a manipulator
	public function listAcl ($obj=NULL)
	/**
	 * Список прав, которыми можно управлять через gui
	 *
	 * FIXME : 2007-11-20 : lucky
	 * Всегда возвращает полный список действий, имеют они отношение к объекту или нет.
	 * Возможно, это и к лучшему %)
	 *
	 * FIXME : 2007-11-20 : lucky
	 * По хорошему нужно считывать из БД,
	 * Который прописывать при инсталляции.
	 *
	 * FIXME : 2007-11-20 : lucky
	 * Можно юзазать для инсталятора.
	 *
	 * @param mixed $obj oid объекта
	 * @access public
	 * @return void
	 */
	{
		$actions = array(
			/*
			'item_create_premod' => array(
				'name' => 'item_create_premod',
				'title' => 'Создать сообщение с премодерацией',
			),
			 */
			'item_create' => array(
				'name' => 'item_create',
				'title' => 'Создавать сообщения',
			),
			'item_read' => array(
				'name' => 'item_read',
				'title' => 'Читать сообщения',
			),
			/*
			'item_edit' => array(
				'name' => 'item_edit',
				'title' => 'Изменить сообщение',
			),
			'item_delete' => array(
				'name' => 'item_delete',
				'title' => 'Удалить сообщение',
			),

			'comment_create_premod' => array(
				'name' => 'comment_create_premod',
				'title' => 'Комментировать с премодерацией',
			),
			 */
			'comment_create' => array(
				'name' => 'comment_create',
				'title' => 'Создавать комментарии',
			),
			/*
			'comment_read' => array(
				'name' => 'comment_read',
				'title' => 'Читать комментарий',
			),
			'comment_edit' => array(
				'name' => 'comment_edit',
				'title' => 'Изменить комментарий',
			),
			'comment_delete' => array(
				'name' => 'comment_delete',
				'title' => 'Удалить комментарий',
			),
			 */
		);

		return $actions;

	} // end of function listAcl
	public function _changeAcl ($obj, $actions, $subj, $mode=false)
	{
		if (!isset($subj)) $subj = $this->acl_data['user_id'];
		// HACK : 2007-11-23 : lucky
		// если субъект не указан
		// или там '0' -- для анонимов
		// ниче не делаем
		if (empty($subj)) return false;

		$acl_list = array();
		foreach($actions as $name=>$permit) {

			$acl = array();
			$acl[ 'object_id'] = $obj;
			$acl['subject_id'] = $subj;
			$acl[       'act'] = $name;

			//$permit = isset($permit) ? ($permit ? 1 : 0) : NULL;

			$acl['opt'] = isset($permit) ? ($permit ? $this->act_allowed : $this->act_disallowed) : $this->act_inherit;
			$acl_list[] = $acl;
		}
		return $this->acl_instance->setAclList($acl_list, $mode);
	} // end of function _changeAcl
	// --- acl --- }}}

	private function getCommunityIdByUrl( $url )
	{
		// если пришли данные о сообществе, то определим id объекта сообщество
		if (strlen($url) > 0) {
			$ql = 'select objects where
				community.community_url = :0
				and objects.type = "fcommunity"
				and objects.is_deleted = 0';
			$rs = $this->store->find($ql, array($url));
			// принимаем, что объект у нас один
			if ($obj = $rs->next()) {
				//сольем всю информацию про коммунити в acl_data
				foreach ( $obj->data as $k=>$v) $this->acl_data[$k] = $v;
				$this->acl_data['community_deleted'] = $obj->is_deleted;
				return $obj->id;
			}
		}
		return NULL;
	}

	private function getRoleArray( $role_oid ) {
		$c['role']['oid']['=='] = $role_oid;
		$rs = $this->store->find( $c );

		if ( !$rs ) {
			return array();
		}

		$obj = $rs->next(); // принимаем, что объект у нас один

		return array( "oid"=>$role_oid, "name"=>$obj->data['name'], "title"=>$obj->data['title'] );
	}

	public function getUserRole( $user_oid ) {
		$result = NULL;
		if ( isset($user_oid) ) {
			// найти путь в t_subject_relations такой чтобы свойства нода на 1-м шаге и на 4-м шаге удовл. опред. условиям
			$result = $this->acl_instance->getPathWCond( array(
				"1"=>array("id"=>$this->regfind("node_role_node_srid")),
				"3"=>array("id_object"=>$user_oid) ), "t_subject_relations", "id", "parent_id", 2
				);
		}

		if (!$result) $role_oid = $this->regfind("anonymous_oid");
			else $role_oid = $result['id_object'];

		return $this->getRoleArray( $role_oid );
	}

	public function getUserRoleInCommunity( $user_id, $comity_oid ) {

		// извлекаем роль юзера на основе правил расположения элементов в дереве
		// да, это жесткая привязка к расположению узлов в дереве
		$result = NULL;
		if ( is_numeric($comity_oid)&&isset($user_id) ) {
		// найти путь в t_subject_relations такой чтобы свойства нода на 1-м шаге и на 4-м шаге удовл. опред. условиям
			$result = $this->acl_instance->getPathWCond( array(
				"1"=>array("id_object"=>$comity_oid),
				"4"=>array("id_object"=>$user_id) ), "t_subject_relations", "id", "parent_id", 3
				);
		}

		if (!$result) {
			if ( $user_id ) {
				$role_oid = $this->regfind("registered_oid");
			} else {
				$role_oid = $this->regfind("anonymous_oid");
			}
		}	else {
			$role_oid = $result['id_object'];
		}

		return $this->getRoleArray( $role_oid );
	}


	// Вернет список разделов и oid в сообществе
	public function getCommunityTdi( $comity_oid ) {

		if ( is_numeric($comity_oid) ) {
			$res = $this->acl_instance->getLevelChildsByOid( $comity_oid );
			return $res;
		}


	}

	public function getCurrentTdi( $tdi_url, $community_oid, $item_oid ) {
		if (isset($tdi_url)) {

			$ql = 'select objects where
				tdi.url = :0
				and tdi.community_oid = :1
				and objects.type = "ftdi"
				and objects.is_deleted = 0';
			$rs = $this->store->find($ql, array($tdi_url, $community_oid));

			$obj = $rs->next(); // принимаем, что объект у нас один

			// отправим объект
			return $obj;

		} elseif (isset($item_oid)) {
			$q = "objects where objects.oid=:0 limit 0,1";
			$rs = $this->store->find($q, array($item_oid));
			if ($rs) {
				$m_tdi = $this->mm->getModel("Tdi", "m");
				return $m_tdi->getTdi($rs->next()->data['tdi_oid']);
			}
		} else return false;
	}

	/**
	 * Явно укажет ассистенту, что в текущем запросе мы имеем дело с $item_oid итемом
	 *
	 * @return void
	 * @author Alexander Sibiryakov
	 **/
	public function setItemOid( $item_oid ) {
		$this->acl_data['id_item'] = $item_oid;
	}


	/**
	 * Удаление объекта независимо от типа объекта, на входе oid объекта, поидее нужно для виджета статистики
	 *
	 * @return boolean
	 * @author Alexander Sibiryakov
	 **/
	public function delete( $oid ) {
		// Взять акл_три найти там $oid,
		// затем всем детям этого элемента прописать тип удаленного deleted_type
		$db = &$this->db;
		$obj = $this->acl_instance->getAclTreeNode($oid);
		if (!isset($obj['id'])) return false;

		$childs = $this->acl_instance->getAllChildsById($obj['id'], "t_acl_tree");
		if (!isset($childs[0]['id'])) return false;

		foreach ($childs as $k=>$chld) {
			$chld_ids[] = "id=".$chld['id'];
		}

		return $db->execute("UPDATE `t_acl_tree` SET `is_deleted`=1 WHERE ".implode(" OR ", $chld_ids));
	}

	/**
	 * Проверки на "удаленность" различных сущностей которые может выводить этот обработчик, вызовет $callback если что-то удалено.
	 *
	 * @return void
	 * @author Alexander Sibiryakov
	 **/
	public function checkDeletions(array $callback) {
		if ($this->acl_data['community_deleted']==1) {
			echo "Тут сообщество удалено.";
			call_user_func( $callback );
			die();
		}
		$tdi_oids = array();
		foreach ($this->acl_data['tdi_list'] as $tdi) $tdi_oids[] = $tdi['id_object'];
		$tdi_types = $this->mm->getModel("Tdi", "m")->getSections($tdi_oids);

		$current_tdi_deleted = false;
		if ($tdi_types) {
			$if_all_deleted = true;
			foreach ($tdi_types as $oid=>$tdi) {
				if ($tdi['is_deleted'] != 1) $if_all_deleted = false;
				if (($oid == $this->acl_data['tdi_oid']) && ($tdi['is_deleted'] == 1)) $current_tdi_deleted = true;
			}
		}
		/*
		if ($if_all_deleted) {
			echo "Все разделы в этой ленте удалены.";
			call_user_func( $callback );
			die();
		} else
		*/
		if ($current_tdi_deleted) {
			echo "Запрашиваемый раздел удален.";
			call_user_func( $callback );
			die();
		}
	}

	public function getRawRolesList() {
		$sql = 'select oid, name from store_class_role';
		$request = $this->db->Query($sql);
		$list = array();
		foreach ( $request as $item ) {
			$list[$item['name']] = $item['oid'];
		}

		return $list;
	}
}


